(function (fruit) {
    var invertedBooleanConverter = {
        convert: function (value) {
            return !value;
        },
        convertBack: function (value) {
            return !value;
        }
    };

    var booleanConverter = {
        convert: function (value) {
            return !!value;
        },
        convertBack: function (value) {
            return !!value;
        }
    };

    /**
     * @class fruit.Binding.
     */
    fruit.define('fruit.Binding', {
        statics: {
            getByPath: function (context, path) {
                if (path) {
                    var tokens = path.split('.'), token;
                    var i = 0, length = tokens.length;
                    var result = context;

                    for (; result && i < length; i++) {
                        token = tokens[i];
                        if (result.get) {
                            result = result.get(token);
                        }
                        else {
                            result = result[token];
                        }
                    }

                    return result;
                }
                else {
                    return context;
                }
            }
        },
        properties: {
            /**
             * Get the target object of current binding.
             */
            target: {
                value: null,
                readonly: true
            },
            /**
             * Get the target path of current binding.
             */
            targetPath: {
                value: '',
                readonly: true
            },
            /**
             * Get the source path of current binding.
             */
            sourcePath: {
                value: '',
                readonly: true
            },
            /**
             * Get a value indicates whether current binding is bi-directional.
             */
            twoWay: {
                value: true,
                readonly: true
            },
            /**
             * Get the formatter for current binding.
             */
            formatter: {
                value: null,
                readonly: true
            },
            /**
             * Get the converter for current binding.
             */
            converter: {
                value: null,
                readonly: true
            },
            /**
             * Get or set the source of current binding.
             */
            source: {
                getter: function () {
                    return this._source;
                },
                setter: function (value) {
                    if (this._source !== value) {
                        var sourcePath = this.sourcePath();
                        this._unbind(this._source, sourcePath);
                        this._source = value;

                        if (value) {
                            this._updateTarget(value, sourcePath);
                            this._bind(value, sourcePath);
                        }
                    }
                }
            }
        },
        methods: {
            init: function (config) {
                var self = this;
                var target = config.target;
                var targetPath = config.targetPath;
                var source = config.source;
                var sourcePath = config.sourcePath;
                var formatter = config.formatter;
                var converter = null;
                this.target(target);
                this.targetPath(targetPath);
                this.formatter(formatter);

                //TODO: twoWay?
                var index = sourcePath.indexOf('!');
                if (index >= 0) {
                    sourcePath = sourcePath.slice(0, index) + sourcePath.slice(index + 1);
                    if (sourcePath[index] == '?') {
                        sourcePath = sourcePath.slice(0, index) + sourcePath.slice(index + 1);
                        converter = invertedBooleanConverter;
                        this.twoWay(false);
                    }
                    else if (sourcePath[index] == '!') {
                        sourcePath = sourcePath.slice(0, index) + sourcePath.slice(index + 1);
                        converter = booleanConverter;
                    }
                    else {
                        converter = invertedBooleanConverter;
                    }
                }

                this.sourcePath(sourcePath);
                this.converter(converter);
                if (config.twoWay !== undefined) {
                    this.twoWay(config.twoWay); //TODO:bug , line 109
                }
                else {
                    var targetProp = target[targetPath];
                    if (targetProp && targetProp.config) {
                        var twoWay = targetProp.config.twoWay;
                        if (twoWay !== undefined) {
                            this.twoWay(twoWay);
                        }
                    }
                }

                if (!this.formatter() && this.twoWay()) {
                    target.on('propertyChanged', function (sender, event) {
                        if (event.property == targetPath) {
                            var value = event.newValue;
                            value = converter ? converter.convertBack(value) : value;
                            self._setPropertyByPath(self.source(), sourcePath, value);
                        }
                    }, this);
                }

                this._initListeners();
                this.source(source);
            },
            destroy: function () {
                this.source(null);
            },
            _initListeners: function () {
                var listeners = this._listeners = {};
                var target = this.target();
                var targetPath = this.targetPath();
                var sourcePath = this.sourcePath();
                var keys = sourcePath.split('.'), key, subPath, fullPath;
                var formatter = this.formatter();
                var converter = this.converter();
                var self = this;
                var i = 0, length = keys.length;

                if (target.can(targetPath)) {
                    this._updateTarget = function (context, path) {
                        var source = context;
                        var sourcePath = path;

                        var index = sourcePath.lastIndexOf('.');
                        if (index > 0) {
                            source = this._getPropertyByPath(source, sourcePath.slice(0, index));
                            sourcePath = sourcePath.slice(index + 1);
                        }

                        target.upon(targetPath, source && source[sourcePath], source);
                    };

                    length--;
                }
                else {
                    this._updateTarget = function (context, path) {
                        var value = this._getPropertyByPath(context, path);
                        value = formatter ? formatter.format(value) : value;
                        value = converter ? converter.convert(value) : value;
                        target.setByPath(targetPath, value);
                    }
                }

                for (; i < length; i++) {
                    key = keys[i];
                    fullPath = keys.slice(i).join('.');
                    subPath = keys.slice(i + 1).join('.');

                    listeners[fullPath] = (function (key, subPath) {
                        return function (sender, event) {
                            if (event.property === key) {
                                self._unbind(event.oldValue, subPath);
                                self._updateTarget(event.newValue, subPath);
                                self._bind(event.newValue, subPath);
                            }
                        }
                    })(key, subPath);
                }
            },
            _bind: function (context, path) {
                if (context && path) {
                    var source = context;
                    var keys = path.split('.'), key;
                    var listener;

                    for (var i = 0, length = keys.length; source && i < length; i++) {
                        key = keys[i];
                        listener = this._listeners[keys.slice(i).join('.')];

                        if (listener) {
                            if (source.on) {
                                source.on('propertyChanged', listener, this);
                            }
                            if (source.get) {
                                source = source.get(key);
                            }
                            else {
                                source = source[key];
                            }
                        }
                    }
                }
            },
            _unbind: function (context, path) {
                if (context && path) {
                    var source = context;
                    var keys = path.split('.'), key;
                    var listener;

                    for (var i = 0, length = keys.length; source && i < length; i++) {
                        key = keys[i];
                        listener = this._listeners[keys.slice(i).join('.')];

                        if (listener) {
                            if (source.off) {
                                source.off('propertyChanged', listener, this);
                            }
                            if (source.get) {
                                source = source.get(key);
                            }
                            else {
                                source = source[key];
                            }
                        }
                    }
                }
            },
            _getPropertyByPath: function (context, path) {
                if (path) {
                    var tokens = path.split('.'), token;
                    var i = 0, length = tokens.length;
                    var result = context;

                    for (; result && i < length; i++) {
                        token = tokens[i];
                        if (result.get) {
                            result = result.get(token);
                        }
                        else {
                            result = result[token];
                        }
                    }

                    return result;
                }
                else {
                    return context;
                }
            },
            _setPropertyByPath: function (context, path, value) {
                var tokens = path.split('.'), token;
                var i = 0, length = tokens.length;
                var target = context;

                for (; target && i < length - 1; i++) {
                    token = tokens[i];
                    if (target.get) {
                        target = target.get(token);
                    }
                    else {
                        target = target[token];
                    }
                }

                token = tokens[length - 1];

                if (target) {
                    if (target.set) {
                        return target.set(token, value);
                    }
                    else {
                        return target[token] = value;
                    }
                }
            }
        }
    });
})(fruit);